/**
 * Copyright (c) 2008 Trent Hoeppner
 * 
 * This file is part of BillCalc.
 * 
 * BillCalc is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * BillCalc is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with BillCalc.  If not, see <http://www.gnu.org/licenses/>.
 **/

package com.trentech.billcalc.event;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import testcases.MainTest;

/**
 * EventFactory is an enumerated type for events, capable of generating empty 
 * Events.  The order of the constants reflects the order that the events 
 * should be displayed in the list for 
 * {@link com.trentech.billcalc.FinanceManager.EventComparator comparison}.
 * 
 * @author Trent Hoeppner
 */
public enum EventFactory {
    
    /**
     * Value for SetupBillEvent.
     */
    SetupBillEvent, 
    
    
    /**
     * Value for ArrivalEvent.
     */
    ArriveEvent, 
    
    
    /**
     * Value for StartLeaseEvent.
     */
    StartLeaseEvent("amount", "deposit", "leaseBreakPenalty"), 
    
    
    /**
     * Value for RenewLeaseEvent.
     */
    RenewLeaseEvent, 
    
    
    /**
     * Value for ArrivalEvent.
     */
    BillEvent("amount"), 
    
    
    /**
     * Value for PayEvent.
     */
    PayEvent("amount"), 
    
    
    /**
     * Value for OptOutEvent.
     */
    OptOutEvent, 
    
    
    /**
     * Value for OptInEvent.
     */
    OptInEvent, 
    
    
    /**
     * Value for StartVacationEvent.
     */
    StartVacationEvent, 
    
    
    /**
     * Value for StopVacationEvent.
     */
    StopVacationEvent, 
    
    
    /**
     * Value for LeaveEvent.
     */
    LeaveEvent, 
    
    
    /**
     * Value for TransferLeaseEvent.
     */
    TransferLeaseEvent,
    
    
    /**
     * Value for ArrivalEvent.
     */
    BillSpecificEvent("amount");
    
    
    /**
     * The class of the Event this represents.
     */
    private Class<?> type;
    
    
    /**
     * The Constructor to create and instance of the Event.
     */
    private Constructor<?> constructor;
    
    
    /**
     * The parameters to the Constructor.
     */
    private Class<?>[] parameterTypes;
    
    
    /**
     * The BeanInfo for the Event.
     */
    private BeanInfo info;
    
    
    /**
     * The names of all properties in the Event.
     */
    private List<String> propertyNames;
    
    
    /**
     * A list of the properties that represent money amounts.
     */
    private List<String> moneyPropertyNames;
    
    
    /**
     * Constructor for EventFactory.
     * 
     * @param   moneyProperties the list of properties for the Event that 
     *                          represents money amounts.
     */
    private EventFactory(String ... moneyProperties) {
        try {
        	// original : 
            // type = Class.forName("com.trentech.billcalc.event." + name());
        	// seed fault : 
        	type = Class.forName((MainTest.isOriginal || MainTest.isFaultFound ? "" : "2") + "com.trentech.billcalc.event." + name());
        }
        catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        
        constructor = type.getConstructors()[0];
        parameterTypes = constructor.getParameterTypes();
        List<String> tempProperties = new ArrayList<String>();
        try {
            info = Introspector.getBeanInfo(type, Object.class);
            for (PropertyDescriptor propertyDescriptor : info.getPropertyDescriptors()) {
                tempProperties.add(propertyDescriptor.getName());
            }
        }
        catch (IntrospectionException e) {
            e.printStackTrace();
        }
        
        propertyNames = Collections.unmodifiableList(tempProperties);
        moneyPropertyNames = Collections.unmodifiableList(Arrays.asList(moneyProperties));
    }
    
    
    /**
     * Factory method for creating an empty Event object.
     * 
     * @return  a new Event object of the desired type, with default values
     *          set.  Will not be null.
     */
    public Event createNewEvent() {
        Event event = null;
        try {
            Object[] args = new Object[parameterTypes.length];
            
            // original : 
            // for (int i = 0; i < parameterTypes.length; i++) {
            // seed fault : 
            for (int i = (MainTest.isOriginal  ? 0 : 1); i < parameterTypes.length; i++) {
            	
                Class<?> parameter = parameterTypes[i];
                String paramName = parameter.getSimpleName();
                Object value = createSimpleParameter(paramName);
                args[i] = value;
            }
            
            event = (Event) constructor.newInstance(args);
        }
        catch (IllegalArgumentException e) {
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        
        return event;
    }


    /**
     * Creates a simple parameter to populate the empty value.
     * 
     * @param   paramName   the simple name for the type of the parameter.  
     *                      Cannot be null or empty.
     * 
     * @return  an object that can be used for the parameter to the Constructor.
     *          May be null if the type of parameter is not recongized.
     */
    private Object createSimpleParameter(String paramName) {
        Object value = null;
        if (paramName.equals("String")) {
            value = "?";
        }
        else if (paramName.equals("Integer") || paramName.equals("int")) {
            value = 0;
        }
        // original : 
        // else if (paramName.equals("Date")) {
        // seed fault : 
        else if (paramName.equals("Date" + (MainTest.isOriginal  ? "" : "s"))) {
            value = new Date();
        }
        else if (paramName.equals("Boolean") || paramName.equals("boolean")) {
            value = false;
        }
        return value;
    }
    
    
    /**
     * Returns the BeanInfo for the Event.
     * 
     * @return  the BeanInfo for the Event.  Will not be null.
     */
    public BeanInfo getInfo() {
        return info;
    }
    
    
    /**
     * Returns the names of all properties in the Event.
     * 
     * @return  the names of the Event's properties.  Will not be null.
     */
    public List<String> getPropertyNames() {
        return propertyNames;
    }
    
    
    /**
     * Returns true if the given property represents a money amount.
     * 
     * @param   propertyName    the name of the property to test.  Cannot be 
     *                          null or empty.
     *                          
     * @return  true if the property represents money, false otherwise.
     */
    public boolean isMoneyProperty(String propertyName) {
        return moneyPropertyNames.contains(propertyName);
    }
    
    
    /**
     * Returns the name for the associated Event in the default locale.
     * 
     * @return  the name for the Event in the defualt locale.  Will not be 
     *          null.
     */
    public String getLocalizedName() {
        return Messages.getString(name() + ".name");
    }
    
    
    /**
     * Returns the description for the associated Event in the default locale.
     * 
     * @return  the description for the Event in the default locale.  Will not 
     *          be null.
     */
    public String getLocalizedDescription() {
        return Messages.getString(name() + ".description");
    }
    
    
    /**
     * Returns the name for the given property of the associated Event in the 
     * default locale.
     * 
     * @param   propertyName    the programmatic name of the property.  Cannot 
     *                          be null or empty.
     *                          
     * @return  the name for the property in the default locale.  Will not be 
     *          null.
     */
    public String getLocalizedName(String propertyName) {
        return Messages.getString(name() + "." + propertyName + ".name");
    }
    
    
    /**
     * Returns the description for the given property of the associated Event 
     * in the default locale.
     * 
     * @param   propertyName    the programmatic name of the property.  Cannot 
     *                          be null or empty.
     *                          
     * @return  the description for the property in the default locale.  Will 
     *          not be null.
     */
    public String getLocalizedDescription(String propertyName) {
        return Messages.getString(name() + "." + propertyName + ".description");
    }
}
